import panel as pn # 用于图形化界面
from collections import defaultdict

import openai

import json
with open(".\part2\chapter8\products.json", "r", encoding='utf-8') as file:
    products = json.load(file)
pn.extension()
panels = [] # collect display
# 系统信息
context = [ {'role':'system', 'content':"You are Service Assistant"} ]
import openai
import os
import openai
from dotenv import load_dotenv, find_dotenv
from openai import OpenAI
def get_openai_key():
    _ = load_dotenv(find_dotenv())
    return os.environ['OPENAI_API_KEY']


client = OpenAI(
    api_key=get_openai_key(),  # This is the default and can be omitted
    base_url="https://api.proxyxai.com/v1"
)

def get_completion_from_messages(messages, model="gpt-4o", temperature=0, max_tokens=500):
    response = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature, # 控制模型输出的随机程度
            max_tokens=max_tokens,
            )
    return response.choices[0].message.content


def get_product_by_name(name):
    """
    根据产品名称获取产品
    参数:
    name: 产品名称
    """
    return products.get(name, None)
def get_products_by_category(category):
    """
    根据类别获取产品
    参数:
    category: 产品类别
    """
    return [product for product in products.values() if product["类别"] ==category]

def read_string_to_list(input_string):
    """
    将输入的字符串转换为 Python 列表。
    参数:
    input_string: 输入的字符串，应为有效的 JSON 格式。
    返回:
    list 或 None: 如果输入字符串有效，则返回对应的 Python 列表，否则返回 None。
    """
    if input_string is None:
        return None
    try:
        # 将输入字符串中的单引号替换为双引号，以满足 JSON 格式的要求
        input_string = input_string.replace("'", "\"")
        data = json.loads(input_string)
        return data
    except json.JSONDecodeError:
        print("Error: Invalid JSON string")
        return None

def extract_product_and_category(input_string, products_and_category):
    """

    """
    delimiter = "####"
    system_message = f"""
        您将获得客户服务查询。
        客户服务查询将使用{delimiter}字符分隔。
        输出一个可解析的Python列表，列表每一个元素是一个JSON对象，每个对象具有以下格式：
        'category': <包括以下几个类别：Computers and Laptops，Smartphones and Accessories，
        Televisions and Home Theater Systems，Gaming Consoles and Accessories，Audio Equipment，Cameras and Camcorders>
        以及
        'products': <必须是下面的允许产品列表中找到的产品列表>

        其中类别和产品必须在客户服务查询中找到。
        如果提到了产品，则必须将其与允许产品列表中的正确类别关联。
        如果未找到任何产品或类别，则输出一个空列表。
        除了列表外，不要输出其他任何信息！

        允许的产品以JSON格式提供。
        每个项的键表示类别。
        每个项的值是该类别中的产品列表。
        允许的产品：{products_and_category}

        """

    messages = [{'role': 'system', 'content': system_message},
                {'role': 'user', 'content': f"{delimiter}{input_string}{delimiter}"}
                ]

    return get_completion_from_messages(messages)

# 从商品数据中获取
def get_products():
    with open(".\part2\chapter8\products.json", 'r', encoding='utf-8') as file:
        products = json.load(file)
    return products

def generate_output_string(data_list):
    output_string = ""

    if data_list is None:
        return output_string
    # print(data_list)
    for data in data_list:
        try:
            if "products" in data:
                # print(data)
                products_list = data["products"]
                for product_name in products_list:
                    product = get_product_by_name(product_name)
                    if product:
                        output_string += json.dumps(product, indent=4) + "\n"
                    else:
                        print(f"错误: 商品 '{product_name}' 没有找到")
            elif "category" in data:
                category_name = data["category"]
                category_products = get_products_by_category(category_name)
                for product in category_products:
                    output_string += json.dumps(product, indent=4) + "\n"
            else:
                print("错误：非法的商品格式")
        except Exception as e:
            print(f"Error: {e}")

    return output_string

def get_products_and_category():
    """
    具体原理参见第五节课
    """
    products = get_products()
    products_by_category = defaultdict(list)
    for product_name, product_info in products.items():
        category = product_info.get('category')
        if category:
            products_by_category[category].append(product_info.get('name'))

    return dict(products_by_category)





'''
注意：限于模型对中文理解能力较弱，中文 Prompt 可能会随机出现不成功，可以多次运行；也非常欢迎同学
探究更稳定的中文 Prompt
'''
def process_user_message_ch(user_input, all_messages, debug=True):
    """
    对用户信息进行预处理
    参数:
    user_input : 用户输入
    all_messages : 历史信息
    debug : 是否开启 DEBUG 模式,默认开启
    """
    # 分隔符
    delimiter = "```"

    # 第一步: 使用 OpenAI 的 Moderation API 检查用户输入是否合规或者是一个注入的 Prompt
    # response = openai.Moderation.create(input=user_input)
    # moderation_output = response["results"][0]
    # # 经过 Moderation API 检查该输入不合规
    # if moderation_output["flagged"]:
    #     print("第一步：输入被 Moderation 拒绝")
    #     return "抱歉，您的请求不合规"
    # 如果开启了 DEBUG 模式，打印实时进度
    if debug: print("第一步：输入通过 Moderation 检查")

    # 第二步：抽取出商品和对应的目录，类似于之前课程中的方法，做了一个封装
    category_and_product_response = extract_product_and_category(user_input,get_products_and_category())
    #print(category_and_product_response)
    # 将抽取出来的字符串转化为列表
    category_and_product_list = read_string_to_list(category_and_product_response)
    #print(category_and_product_list)
    if debug: print("第二步：抽取出商品列表")


    # 第三步：查找商品对应信息
    product_information = generate_output_string(category_and_product_list)
    if debug: print("第三步：查找抽取出的商品信息")


    # 第四步：根据信息生成回答
    system_message = f"""
    您是一家大型电子商店的客户服务助理。\
    请以友好和乐于助人的语气回答问题，并提供简洁明了的答案。\
    请确保向用户提出相关的后续问题。
    """
    # 插入 message
    messages = [
    {'role': 'system', 'content': system_message},
    {'role': 'user', 'content': f"{delimiter}{user_input}{delimiter}"},
    {'role': 'assistant', 'content': f"相关商品信息:\n{product_information}"}
    ]

    # 通过附加 all_messages 实现多轮对话
    final_response = get_completion_from_messages(all_messages + messages)
    if debug:print("第四步：生成用户回答")
    # 将该轮信息加入到历史信息中
    all_messages = all_messages + messages[1:]


    # 第五步：基于 Moderation API 检查输出是否合规
    # response = openai.Moderation.create(input=final_response)
    # moderation_output = response["results"][0]
    # # 输出不合规
    # if moderation_output["flagged"]:
    #     if debug: print("第五步：输出被 Moderation 拒绝")
    #     return "抱歉，我们不能提供该信息"
    if debug: print("第五步：输出经过 Moderation 检查")

    # 第六步：模型检查是否很好地回答了用户问题
    user_message = f"""
    用户信息: {delimiter}{user_input}{delimiter}
    代理回复: {delimiter}{final_response}{delimiter}
    回复是否足够回答问题
    如果足够，回答 Y
    如果不足够，回答 N
    仅回答上述字母即可
    """
    # print(final_response)
    messages = [
    {'role': 'system', 'content': system_message},
    {'role': 'user', 'content': user_message}
    ]
    # 要求模型评估回答
    evaluation_response = get_completion_from_messages(messages)
    # print(evaluation_response)
    if debug: print("第六步：模型评估该回答")
    # 第七步：如果评估为 Y，输出回答；如果评估为 N，反馈将由人工修正答案
    if "Y" in evaluation_response: # 使用 in 来避免模型可能生成 Yes
        if debug: print("第七步：模型赞同了该回答.")
        return final_response, all_messages
    else:
        if debug: print("第七步：模型不赞成该回答.")
        neg_str = "很抱歉，我无法提供您所需的信息。我将为您转接到一位人工客服代表以获取进一步帮助。"
        return neg_str, all_messages



# 调用中文 Prompt 版本
def collect_messages_ch(debug=True):
    """
    用于收集用户的输入并生成助手的回答
    参数：
    debug: 用于觉得是否开启调试模式
    """
    user_input = inp.value_input
    if debug: print(f"User Input = {user_input}")
    if user_input == "":
        return
    inp.value = ''
    global context
    # 调用 process_user_message 函数
    response, context = process_user_message_ch(user_input, context, debug=False)
    # print(response)
    context.append({'role':'assistant', 'content':f"{response}"})
    panels.append(pn.Row('User:', pn.pane.Markdown(user_input, width=600)))
    # 添加助手消息，并使用 HTML 标签设置背景颜色
    response_with_style = f'<div style="background-color: #F6F6F6; padding: 10px; width: 600px;">{response}</div>'
    panels.append(
        pn.Row('Assistant:', pn.pane.HTML(response_with_style))
    )
    return pn.Column(*panels) # 包含了所有的对话信息



inp = pn.widgets.TextInput( placeholder='Enter text here…')
button_conversation = pn.widgets.Button(name="Service Assistant")
interactive_conversation = pn.bind(collect_messages_ch, button_conversation)
dashboard = pn.Column(
inp,
pn.Row(button_conversation),
pn.panel(interactive_conversation, loading_indicator=True, height=300),
)

dashboard.servable()

# 在命令行中运行：conda activate your_env
# 进入项目根目录：
# 运行脚本：panel serve .\part2\chapter8\example2.py --autoreload
# 打开浏览器：http://localhost:5006/example2
# 然后就可以开始你的订餐之旅了！